<?php
/**
 * Created by PhpStorm.
 * User: whwyy
 * Date: 2018/4/4 0004
 * Time: 14:42
 */
declare(strict_types=1);

namespace Database;

use Closure;
use Database\Traits\QueryTrait;
use Kiri\Di\Context;
use Swoole\Coroutine;

/**
 * Class ActiveQuery
 * @package Database
 */
class ActiveQuery extends QueryTrait implements ISqlBuilder
{


	public bool     $asArray = FALSE;
	protected mixed $_mock   = NULL;


	/**
	 * @param bool $asArray
	 *
	 * @return static
	 */
	public function asArray(bool $asArray = TRUE): static
	{
		$this->asArray = $asArray;
		return $this;
	}

	/**
	 * @param array $methods
	 *
	 * @return $this
	 */
	public function with(array $methods): static
	{
		$this->modelClass->setWith($methods);
		return $this;
	}


	/**
	 * @return ModelInterface|array|null|bool
	 * @throws
	 */
	public function first(): ModelInterface|null|array|bool
	{
		$data = $this->buildCommand($this->builder->one())->one();
		if (is_array($data)) {
			return $this->populate($data);
		}
		return NULL;
	}

	/**
	 * @return bool|Collection
	 */
	public function get(): bool|Collection
	{
		$data = $this->buildCommand($this->builder->all())->all();
		if (is_array($data)) {
			return new Collection($this, $this->modelClass, $data);
		}
		return FALSE;
	}


	/**
	 * @param string    $column
	 * @param int|float $amount
	 *
	 * @return bool
	 */
	public function increment(string $column, int|float $amount = 1): bool
	{
		return (bool)$this->buildCommand($this->builder->mathematics([$column => $amount]))->exec();
	}


	/**
	 * @param array $attributes
	 *
	 * @return bool
	 */
	public function increments(array $attributes): bool
	{
		return (bool)$this->buildCommand($this->builder->mathematics($attributes))->exec();
	}


	/**
	 * @param string    $column
	 * @param int|float $amount
	 *
	 * @return bool
	 */
	public function decrement(string $column, int|float $amount = 1): bool
	{
		return (bool)$this->buildCommand($this->builder->mathematics([$column => $amount], '-'))->exec();
	}


	/**
	 * @param array $attributes
	 *
	 * @return bool
	 */
	public function decrements(array $attributes): bool
	{
		return (bool)$this->buildCommand($this->builder->mathematics($attributes, '-'))->exec();
	}


	/**
	 * @throws
	 */
	public function flush(): bool
	{
		return (bool)$this->buildCommand($this->builder->truncate())->exec();
	}


	/**
	 * @param int     $size
	 * @param Closure $closure
	 *
	 * @return void
	 */
	public function chunk(int $size, Closure $closure): void
	{
		if ($this->offset === -1) $this->offset = 0;
		$data = $this->offset($this->offset)->limit($size)->get();
		if (!$data || $data->isEmpty()) {
			return;
		}
		if (Context::inCoroutine()) {
			Coroutine::create(fn() => $closure($data));
		} else {
			call_user_func($closure, $data);
		}
		$this->offset += $size;
		$this->chunk($size, $closure);
	}

	/**
	 * @param string      $field
	 * @param string|null $setKey
	 *
	 * @return array|null
	 */
	public function column(string $field, ?string $setKey = NULL): ?array
	{
		return $this->get()->column($field, $setKey);
	}


	/**
	 * @param mixed $value
	 *
	 * @return $this
	 */
	public function withMock(mixed $value): static
	{
		$this->_mock = $value;
		return $this;
	}


	/**
	 * @return mixed
	 */
	public function mock(): mixed
	{
		return $this->_mock;
	}


	/**
	 * @param array $data
	 *
	 * @return ModelInterface|array
	 * @throws
	 */
	public function populate(array $data): ModelInterface|array
	{
		$model = $this->modelClass->populates($data);

		return $this->asArray ? $model->toArray() : $model;
	}


	/**
	 * @return string
	 */
	public function toSql(): string
	{
		return $this->builder->get();
	}


	/**
	 * @return int
	 * @throws
	 */
	public function count(): int
	{
		return $this->buildCommand($this->builder->count())->rowCount();
	}


	/**
	 * @param array $data
	 *
	 * @return bool
	 * @throws
	 */
	public function update(array $data): bool
	{
		if (count($data) < 1) {
			return TRUE;
		}
		$generate = $this->builder->update($data);
		if (!is_bool($generate)) {
			return (bool)$this->buildCommand($generate)->exec();
		} else {
			return $generate;
		}
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public function insert(array $data): bool
	{
		$sql = $this->builder->insert($data, isset($data[0]));

		return (bool)$this->buildCommand($sql)->exec();
	}


	/**
	 * @param string $filed
	 *
	 * @return mixed
	 * @throws
	 */
	public function value(string $filed): mixed
	{
		return $this->first()[$filed] ?? NULL;
	}

	/**
	 * @return bool
	 * @throws
	 */
	public function exists(): bool
	{
		return $this->buildCommand($this->limit(1)->builder->exists())->exists();
	}


	/**
	 * @param string $sql
	 * @param array  $params
	 *
	 * @return int|bool
	 */
	public function execute(string $sql, array $params = []): int|bool
	{
		return $this->buildCommand($sql, $params)->exec();
	}


	/**
	 * @return bool
	 */
	public function delete(): bool
	{
		return $this->buildCommand($this->builder->delete())->delete();
	}
}
